#include "spl.includes.h"
#include "splib.h"

using namespace SPLib;
using namespace SPLib::Ole;

// performs a memory copy of the data at the given handle
// data:            a pointer to the data to copy
static HGLOBAL Copy(HGLOBAL data)
{
    size_t length = GlobalSize(data);
    PVOID pData = GlobalLock(data);
    PVOID pCopy = GlobalAlloc(GMEM_FIXED, length);

    memcpy(pCopy, pData, length);
    GlobalUnlock(data);

    return pCopy;
}

DataObject::DataObject(FORMATETC *fmEtc, STGMEDIUM *stgMedium, int count)
{
    m_lRefCount = 1;
    m_nNumFormats = count;
    m_pFormatEtc = new FORMATETC[count];
    m_pStgMedium = new STGMEDIUM[count];

    for (int i = 0; i < count; ++i) {
        m_pFormatEtc[i] = fmEtc[i];
        m_pStgMedium[i] = stgMedium[i];
    }
}

DataObject::~DataObject()
{
    delete[] m_pFormatEtc;
    delete[] m_pStgMedium;
}

ULONG DataObject::AddRef()
{
    return InterlockedIncrement(&m_lRefCount);
}

ULONG DataObject::Release()
{
    LONG count = InterlockedDecrement(&m_lRefCount);

    if (count) {
        return count;
    } else {
        delete this;
        return 0;
    }
}

HRESULT DataObject::QueryInterface(REFIID iid, void **ppvObjectOut)
{
    if (iid == IID_IDataObject || iid == IID_IUnknown) {
        AddRef();
        *ppvObjectOut = this;
        return S_OK;
    } else {
        *ppvObjectOut = 0;
        return E_NOINTERFACE;
    }
}

int DataObject::LookupFormatEtc(FORMATETC *pFormatEtc)
{
    for (int i = 0; i < m_nNumFormats; ++i) {
        if ((pFormatEtc->tymed & m_pFormatEtc[i].tymed) &&
            pFormatEtc->cfFormat == m_pFormatEtc[i].cfFormat &&
            pFormatEtc->dwAspect == m_pFormatEtc[i].dwAspect)
            return i;
    }

    return -1;
}

HRESULT WINAPI DataObject::GetData(FORMATETC *pFormatEtc, STGMEDIUM *pMedium)
{
    int idFormat;

    if ((idFormat = LookupFormatEtc(pFormatEtc)) == -1) {
        return DV_E_FORMATETC;
    }

    pMedium->tymed = m_pFormatEtc[idFormat].tymed;
    pMedium->pUnkForRelease = 0;

    if (m_pFormatEtc[idFormat].tymed == TYMED_HGLOBAL) {
        pMedium->hGlobal = Copy(m_pStgMedium[idFormat].hGlobal);
    }

    return S_OK;
}

HRESULT WINAPI DataObject::SetData(FORMATETC *pFormatEtc,
    STGMEDIUM *pMedium, BOOL fRelease)
{
    return E_NOTIMPL;
}

HRESULT WINAPI DataObject::GetDataHere(FORMATETC *pFormatEtc, STGMEDIUM *pMedium)
{
    return DATA_E_FORMATETC;
}

HRESULT WINAPI DataObject::QueryGetData(FORMATETC *pFormatEtc)
{
    return LookupFormatEtc(pFormatEtc) == -1 ? DV_E_FORMATETC : S_OK;
}

HRESULT WINAPI DataObject::GetCanonicalFormatEtc(
    FORMATETC *pFormatEct, FORMATETC *pFormatEtcOut)
{
    pFormatEtcOut->ptd = NULL;
    return E_NOTIMPL;
}

HRESULT WINAPI DataObject::EnumFormatEtc(DWORD dwDirection,
    IEnumFORMATETC **ppEnumFormatEtcOut)
{
    if (dwDirection == DATADIR_GET) {
        *ppEnumFormatEtcOut = new ::EnumFormatEtc(m_pFormatEtc, m_nNumFormats);
        return *ppEnumFormatEtcOut > 0 ? S_OK : E_OUTOFMEMORY;
    } else {
        return E_NOTIMPL;
    }
}

HRESULT WINAPI DataObject::DAdvise(FORMATETC *pFormatEtc,
    DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection)
{
    return OLE_E_ADVISENOTSUPPORTED;
}

HRESULT WINAPI DataObject::DUnadvise(DWORD dwConnection)
{
    return OLE_E_ADVISENOTSUPPORTED;
}

HRESULT WINAPI DataObject::EnumDAdvise(IEnumSTATDATA **ppEnumAdvise)
{
    return OLE_E_ADVISENOTSUPPORTED;
}